/*
 * Copyright (c) 1990, 2021, shwfed and/or its affiliates. All rights reserved.
 *
 *
 */
package cn.junglead.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * RSA 双向非对称加密算法工具.
 * 
 * @author Gary.Wang
 * @version 1.0
 */
public final class RSAUtil {

	private final static Logger logger = LoggerFactory.getLogger(RSAUtil.class);
	
	/**
	 * RSA 最大加密明文长度
	 */
	private static final int MAX_ENCRYPT_BLOCK = 117;
    
    /**
     * RSA 最大解密密文长度
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
	
	/**
	 * 公钥
	 */
	public static final String PUBLIC_KEY = "RSAPublicKey";
	
	/**
	 * 私钥
	 */
	public static final String PRIVATE_KEY = "RSAPrivateKey";
	
	/**
	 * 签名算法还可以取值： MD5WithRSA
	 */
	public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
	
	/**
	 * 公钥字符串转换为公钥对象
	 *
	 * @param publicKeyValue 公钥文本
	 * @return 返回转换后的公钥对象
	 * @throws InvalidKeySpecException 错误的Key指定异常
	 * @throws NoSuchAlgorithmException 错误的算法异常
	 */
	private static RSAPublicKey convertPublicKey(String publicKeyValue) throws InvalidKeySpecException, NoSuchAlgorithmException {
		byte[] input = Base64.getDecoder().decode(publicKeyValue);
		return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(input));
	}
	
	/**
	 * 私钥字符串转换为私钥对象
	 *
	 * @param privateKeyValue 私钥文本
	 * @return 返回转换后的私钥对象
	 * @throws InvalidKeySpecException 错误的Key指定异常
	 * @throws NoSuchAlgorithmException 错误的算法异常
	 */
	private static RSAPrivateKey convertPrivateKey(String privateKeyValue) throws InvalidKeySpecException, NoSuchAlgorithmException {
		byte[] input = Base64.getDecoder().decode(privateKeyValue);
		return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(input));
	}
    
    /**
     * 生成RSA双向非对称加密的密钥对
     *
     * @return 返回生成的 RSA 密钥对，其中 RSAPublicKey 为公钥键名， RSAPrivateKey 为私钥键名。
     */
    public static Map<String, Object> generateKeygenPair() {
    	KeyPairGenerator keyPairGen = null;
		try {
			keyPairGen = KeyPairGenerator.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			logger.error("RSA 生成密钥对发生错误");
		}
		if (null != keyPairGen) {
			keyPairGen.initialize(1024);
	        KeyPair keyPair = keyPairGen.generateKeyPair();
	        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
	        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
	        Map<String, Object> keyMap = new HashMap<>(2);
	        keyMap.put(PUBLIC_KEY, publicKey);
	        keyMap.put(PRIVATE_KEY, privateKey);
	        return keyMap;
		}
		return null;
    }

	/**
	 * RSA 加密
	 *
	 * @param plaintext 待加密的明文文本
	 * @param publicKey 公钥对象
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, RSAPublicKey publicKey) {
		try {
			byte[] input = plaintext.getBytes(StandardCharsets.UTF_8);
			if (input.length > MAX_ENCRYPT_BLOCK) {
				throw new CipherException("RSA 加密字符串长度不能大于 117 位");
			}
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			return Base64.getEncoder().encodeToString(cipher.doFinal(input));
		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | CipherException e) {
			logger.error("RSA 加密发生错误：{}", e.getMessage());
		}
		return null;
    }
    
    /**
     * RSA 加密
     *
     * @param plaintext 待加密的明文文本
     * @param publicKeyValue 公钥文本
     * @return 返回加密后的结果
     */
    public static String encrypt(String plaintext, String publicKeyValue) {
		try {
			return encrypt(plaintext, RSAUtil.convertPublicKey(publicKeyValue));
		} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
			logger.error("RSA 加密发生错误：{}", e.getMessage());
		}
		return null;
    }
    
    /**
     * RSA 解密
     *
     * @param ciphertext 待解密的密文文本
     * @param privateKey 私钥对象
     * @return 返回解密后的结果
     */
    public static String decrypt(String ciphertext, RSAPrivateKey privateKey) {
    	try {
    		byte[] input = Base64.getDecoder().decode(ciphertext);
    		if (input.length > MAX_DECRYPT_BLOCK) {
    			throw new CipherException("RSA 解密字符串长度不能大于 128 位");
    		}
    		Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			return new String(cipher.doFinal(input), StandardCharsets.UTF_8);
    		
    	} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | CipherException e) {
			logger.error("RSA 加密发生错误：{}", e.getMessage());
		}
    	return null;
    }
    
    /**
     * RSA 解密
     *
     * @param ciphertext 待解密的密文文本
     * @param privateKeyValue 私钥文本
     * @return 返回解密后的结果
     */
    public static String decrypt(String ciphertext, String privateKeyValue) {
		try {
			return decrypt(ciphertext, RSAUtil.convertPrivateKey(privateKeyValue));
		} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
			logger.error("RSA 解密发生错误：{}", e.getMessage());
		}
		return null;
	}
    
    /**
     * 获取签名
     *
     * @param content 待签名的内容
     * @param privateKeyValue 私钥文本
     * @return 返回签名
     */
    public static String sign(String content, String privateKeyValue) {
		try {
			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
			signature.initSign(RSAUtil.convertPrivateKey(privateKeyValue));
	    	signature.update(content.getBytes(StandardCharsets.UTF_8));
	    	return Base64.getEncoder().encodeToString(signature.sign());
		} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | SignatureException e) {
			logger.error("RSA 签名发生错误：{}", e.getMessage());
		}
		return null;
    }
    
    /**
     * 验证签名
     *
     * @param content 待验证的内容
     * @param sign 签名内容
     * @param publicKeyValue 公钥文本
     * @return 返回验证结果
     */
    public static boolean checkSign(String content, String sign, String publicKeyValue) {
    	try {
			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
			signature.initVerify(RSAUtil.convertPublicKey(publicKeyValue));
			signature.update(content.getBytes(StandardCharsets.UTF_8));
			return signature.verify(Base64.getDecoder().decode(sign));
		} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | SignatureException e) {
			logger.error("RSA 验证签名发生错误：{}", e.getMessage());
		}
    	return false;
    }
    
    /**
     * 获取公钥文本内容
     *
     * @param keyMap 密钥对
     * @return 返回文本内容
     */
    public static String getPublicKeyValue(Map<String, Object> keyMap) {
    	Key key = (Key) keyMap.get(PUBLIC_KEY);
    	return Base64.getEncoder().encodeToString(key.getEncoded());
    }
    
    /**
     * 
     * 获取私钥文本内容
     *
     * @param keyMap 密钥对
     * @return 返回文本内容
     */
    public static String getPrivateKeyValue(Map<String, Object> keyMap) {
    	Key key = (Key) keyMap.get(PRIVATE_KEY);
    	return Base64.getEncoder().encodeToString(key.getEncoded());
    }
}
